package mymyoux.io.mserveur
{
	import flash.utils.ByteArray;
	
	import mymyoux.events.MEventDispatcher;
	import mymyoux.io.mserveur.events.MBinarySocketEvent;
	import mymyoux.io.mserveur.events.MRoomEvent;
	import mymyoux.io.mserveur.mapping.PyGram;
	import mymyoux.io.mserveur.mcontactClasses.MRoomContacts;
	import mymyoux.io.mserveur.mcontactClasses.People;
	import mymyoux.io.mserveur.mcontactClasses.events.MRoomContactsEvent;
	import mymyoux.io.mserveur.mcontactClasses.events.PeopleEvent;
	import mymyoux.utils.Tampon;
	/**
     * MRoom dispatche l'event people_data quand un client envoie une donnée.
     * @eventType mymyoux.io.mserveur.mcontactClasses.events.PeopleEvent
     */
    [Event(name="people_data", type="mymyoux.io.mserveur.mcontactClasses.events.PeopleEvent")]
	/**
     * MRoom dispatche l'event disconnect quand le client quitte volontairement le salon
     * @eventType mymyoux.io.mserveur.events.MRoomEvent
     */
    [Event(name="disconnect", type="mymyoux.io.mserveur.events.MRoomEvent")]
	 /**
     * MRoom dispatche l'event data
     * à la réception d'un message.
     * @eventType mymyoux.io.mserveur.events.MRoomEvent
     */
    [Event(name="data", type="mymyoux.io.mserveur.events.MRoomEvent")]
        	/**
     * MRoomContacts dispatche l'event people_in quand un client se connecte dans le salon et/ou s'identifie.
     * @eventType mymyoux.io.mserveur.mcontactClasses.events.MRoomContactsEvent
     */
    [Event(name="people_in", type="mymyoux.io.mserveur.mcontactClasses.events.MRoomContactsEvent")]
     /**
     * MRoomContacts dispatche l'event people_out quand un client se déconnecte du salon.
     * @eventType mymyoux.io.mserveur.mcontactClasses.events.MRoomContactsEvent
     */
    [Event(name="people_out", type="mymyoux.io.mserveur.mcontactClasses.events.MRoomContactsEvent")]
	/**
	 * Représente un salon sur un serveur (socket), il permet une utilisation plus aisée
	 * du serveur en le séparant en salons virtuels.
	 * <p><b>Ne Pas instancier MRoom mais utiliser :</b></p>
	 * <pre class="prettyfy">var room:MRoom=MAdvBinSocket.goto(name);</pre>
	 * @see MAdvBinSocket#goto()
	 */
	public class MRoom extends MEventDispatcher
	{
		/**
		 * @private
		 */
		protected var _socket:MBinarySocket;
		/**
		 * @private
		 */
		protected var _name:String;
		/**
		 * L'idx du client.
		 */
		protected var idx:uint;
		/**
		 * Préfixe utilisé sur le serveur pour l'application.
		 */
		protected var pre_idx:String;
		/**
		 * Temporise certaines fonctions.
		 */
		protected var tampon:Tampon;
		/**
		 * Gère la liste de contacts et permet de récupérer les pseudonymes des clients.
		 */
		protected var contactsGlobal:MContacts;
		/**
		 * @private
		 */
		protected var _roomContacts:MRoomContacts
		/**
		 * Crée un objet correspondant à un salon sur le serveur (socket).
		 * Ne doit pas être instancié par le programmeur mais seulement par MAdvBinSocket.
		 * @param name Nom du salon. Il sert d'identifiant.
		 * @param idx Idx du client.
		 * @param pre_idx Préfixe utilisé sur le serveur pour différencier les applications.
		 * @param contacts Liste de contacts, permet la traduction idx<->Pseudonyme.
		 * @see MAdvBinSocket#goto()
		 */
		public function MRoom(name:String,idx:uint,pre_idx:String,contacts:MContacts)
		{
			this.idx=idx;
			_name=name;
			tampon=new Tampon();
			this.pre_idx=pre_idx;
			this.contactsGlobal=contacts;
			_roomContacts=new MRoomContacts(this.contactsGlobal,this);
		}
		/**
		 * Gère la liste de contacts spécifique à ce salon.
		 */
		public function get contacts():MRoomContacts
		{
			return _roomContacts;
		}
		/**
		 * Ferme le salon, le client indique au serveur qu'il quitte le salon.
		 * Il faut requestionner le MAdvBinSocket pour retourner dans le salon,
		 * l'instance présente devient inutilisable.
		 * @see mymyoux.events.MEventDispatcher#destroy()
		 */
		public override function destroy():void
		{
			var message:PyGram=new PyGram();
			message.action=PyGram.CALL_LEAVE;
			message.parameters.push(pre_idx+name);
			socket.write(message);
			this.dispatchEvent(new MRoomEvent(MRoomEvent.DISCONNECT,this,this,-1));
			unloadListeners();
			super.destroy();
			 _socket=null;
		}


		/**
		 * MBinarySocket auquel est rattaché le salon.
		 */
		public function get socket():MBinarySocket
		{
			return _socket;
		}
		public function set socket(value:MBinarySocket):void
		{
			if(_socket!=null)
			{
				unloadListeners();
			}
			_socket=value;
			loadListeners();
			if(_socket.connected)
			{
				goto();
			}
		}
		/**
		 * Permet d'écouter tous les évènements liés au MBinarySocket
		 * @see mymyoux.io.mserveur.events.MBinarySocketEvent
		 */
		protected function loadListeners():void
		{
			socket.addEventListener(MBinarySocketEvent.CONNECT,onConnect);
			socket.addEventListener(MBinarySocketEvent.ENTER,onEnterRoom);
			socket.addEventListener(MBinarySocketEvent.DISCONNECT,onDisconnect);
			socket.addEventListener(MBinarySocketEvent.DATA,onData);
			socket.addEventListener(MBinarySocketEvent.PEOPLE_IN,onPeopleIn);
			socket.addEventListener(MBinarySocketEvent.PEOPLE_OUT,onPeopleOut);
			socket.addEventListener(MBinarySocketEvent.ERROR,onError);
			_roomContacts.addEventListener(MRoomContactsEvent.PEOPLE_IN,onPeopleMove);
			_roomContacts.addEventListener(MRoomContactsEvent.PEOPLE_OUT,onPeopleMove);
		}
		/**
		 * Appelé par MRoomContacts lorsqu'un client se connecte ou déconnecte du salon.
		 * @param event Evenement dispatché par MRoomContacts
		 */
		protected function onPeopleMove(event:MRoomContactsEvent):void
		{
			this.dispatchEvent(event.clone());
		}
		/**
		 * Permet de retirer de l'écoute tous les évènements liés au MBinarySocket
		 * @see mymyoux.io.mserveur.events.MBinarySocketEvent
		 */
		protected function unloadListeners():void
		{
			socket.removeEventListener(MBinarySocketEvent.CONNECT,onConnect);
			socket.removeEventListener(MBinarySocketEvent.ENTER,onEnterRoom);
			socket.removeEventListener(MBinarySocketEvent.DISCONNECT,onDisconnect);
			socket.removeEventListener(MBinarySocketEvent.DATA,onData);
			socket.removeEventListener(MBinarySocketEvent.PEOPLE_IN,onPeopleIn);
			socket.removeEventListener(MBinarySocketEvent.PEOPLE_OUT,onPeopleOut);
			socket.removeEventListener(MBinarySocketEvent.ERROR,onError);
		}
		/**
		 * Booléan indiquant si la connexion avec les serveur (socket) est établie ou non.
		 */
		public function get connected():Boolean
		{
			return socket!=null && socket.connected;
		}
		/**
		 * Nom du salon.
		 */
		public function get name():String
		{
			return _name;
		}
		/**
		 * Appelée lorsque le socket s'est connecté.
		 * @param event Evenement dispatché par le MBinarySocket.
		 */
		protected function onConnect(event:MBinarySocketEvent):void
		{
			goto();
		}
		/**
		 * Appelée lorsque le serveur a confirmé l'entrée dans le salon.
		 * Il fournit également la liste des idx des personnes présentes.
		 * Pour chaque idx un appel à MContacts.addPeople est effectué afin de les référencer
		 * et de récupérer le pseudonyme de chacun d'eux.
		 * @param event Evenement dispatché par le MBinarySocket.
		 * @see MContacts#addPeople()
		 */
		protected function onEnterRoom(event:MBinarySocketEvent):void
		{
			try
			{
				if(event.message.parameters[0]==pre_idx+name)
				{
					contactsGlobal.addPeople(event.message.parameters.slice(1));
					_roomContacts.add(event.message.parameters.slice(1));
					while(tampon.hasNext() && connected)
					{
						var f:Fonction=tampon.next();
						f.func.apply(this,f.params);
					}			
				}
			}
			catch(error:Error)
			{
				trace("enter_room_error [MRoom] :"+error);
			}
		}
		/** 
		 * Appelée par le MBinarySocket lorsque celui-ci rencontre une erreur.
		 * @param event Evenement dispatché par le MBinarySocket.
		 */
		protected function onError(event:MBinarySocketEvent):void
		{
			trace("error_error [MRoom] :"+event);
		}
		/** 
		 * Appelée par le MBinarySocket lorsque celui-ci reçoit des données.
		 * MRoom dispatche ensuite un évènement de type MRoomEvent.DATA contenant les données.
		 * @param event Evenement dispatché par le MBinarySocket.
		 * @see mymyoux.io.mserveur.events.MRoomEvent#DATA
		 * @see mymyoux.io.mserveur.mcontactClasses.events.PeopleEvent#DATA
		 */
		protected function onData(event:MBinarySocketEvent):void{
			if(event.message.parameters[0]==pre_idx+name)
				{
					//PEOPLE_DATA est dispatché par people une fois cet évènement ci dessous dispatché
					this.dispatchEvent(new MRoomEvent(MRoomEvent.DATA,this,event.message.parameters[2],event.message.parameters[1]));
					
				}
		}
		/** 
		 * Appelée par le MBinarySocket lorsqu'une personne se connecte dans le salon.
		 * MRoom appel ensuite MContacts.addPeople(idx)
		 * @param event Evenement dispatché par le MBinarySocket.
		 * @see MContacts#addPeople()
		 */
		protected function onPeopleIn(event:MBinarySocketEvent):void
		{
			if(!event.message.isError() && event.message.parameters.length==2)
			{
				if(event.message.parameters[0]==name)
				{
					contactsGlobal.addPeople(event.message.parameters[1]);
					_roomContacts.add(event.message.parameters[1]);
				}
			}
		}
		/** 
		 * Appelée par le MBinarySocket lorsqu'une personne se déconnecte du salon.
		 * @param event Evenement dispatché par le MBinarySocket.
		 */
		protected function onPeopleOut(event:MBinarySocketEvent):void
		{
			if(!event.message.isError() && event.message.parameters.length==2)
			{
				if(event.message.parameters[0]==name)
				{
					_roomContacts.rem(event.message.parameters[1]);
				}
			}
		}
		/** 
		 * Appelée par le MBinarySocket lorsqu'une déconnexion a eu lieu
		 * @param event Evenement dispatché par le MBinarySocket.
		 */ 
		protected function onDisconnect(event:MBinarySocketEvent):void{
			unloadListeners();
		}
		/**
		 * Active la connexion au salon.
		 */
		protected function goto():void
		{
			var message:PyGram=new PyGram();
			message.action=PyGram.CALL_GOTO;
			message.parameters.push(pre_idx+name);
			socket.write(message);
		}
		/**
		 * Envoie un message à tous les membres du salon.
		 * Pour un meilleur parsage, utiliser flash.net.registerClassAlias.
		 * @param value Un objet de type Object (et n'importe quelle classe) à envoyer au salon.
		 */
		public function sendAll(value:*):void
		{
				var message:PyGram=new PyGram();
				message.action=PyGram.CALL_SEND_ROOM;
				message.parameters.push(name);
				_send(message,value);
		}
		/**
		 * Envoie un message au membre du salon.
		 * Pour un meilleur parsage, utiliser flash.net.registerClassAlias.
		 * @param people Client à qui écrire.
		 * @param value Un objet de type Object (et n'importe quelle classe) à envoyer au salon.
		 */
		public function sendPeople(people:People,value:*):void
		{
			send(people.idx,value);
		}
		/**
		 * Envoie un message au membre du salon ayant pour id idx
		 * Pour un meilleur parsage, utiliser flash.net.registerClassAlias.
		 * @param idx Idx du client.
		 * @param value Un objet de type Object (et n'importe quelle classe) à envoyer au salon.
		 */
		public function send(idx:uint,value:*):void
		{
				var message:PyGram=new PyGram();
				message.action=PyGram.CALL_SEND;
				message.parameters.push(name);
				message.parameters.push(pre_idx+idx);
				_send(message,value);
		}
		/**
		 * Envoie le message en compressant l'objet value dans un bytearray.
		 * Ainsi on peut envoyer une instance de classe inconnue pour le serveur (socket) mais
		 * qui garde sa désérialization intacte pour les clients flash qui utilisent
		 * flash.net.registerClassAlias.
		 * @param message Enveloppe formattée précédemment qui va recevoir value en paramètre.
		 * @param value Message à transmettre typé ou non.
		 */
		protected function _send(message:PyGram,value:*):void
		{
			if(connected)
			{
				var bytearray:ByteArray=new ByteArray();
				bytearray.writeObject(value);
				var len:uint=bytearray.length;
				bytearray.compress();
				if(len<=bytearray.length)
				{
					bytearray.uncompress();
				}
				message.parameters.push(bytearray);
				socket.write(message);
			}else
			{
				
				var param:Array=new Array();
				param.push(message,value);
				tampon.add(new Fonction(_send,param));
			}
		}

	}
}
	internal class Fonction
	{
		protected var _func:Function;
		protected var _params:Array;
		public function Fonction(func:Function,params:Array=null)
		{
			_func=func;
			_params=params;
		}
		public function get func():Function
		{
			return _func;
		}
		public function get params():Array
		{
			return _params;
		}
	}